/**
 * @file timer_operation.c
 * @author your name (you@domain.com)
 * @brief 2025年9月2日-change:兼容掉电保持，时间溢出后的正确处理
 * @version 0.1
 * @date 2025-04-30
 *
 * @copyright Copyright (c) 2025
 *
 */
#include <stm32f4xx.h>
#include "timer_operation.h"
#include "xmzTypeDefine.h"
#include "db_raw_access.h"

/**
 * @brief 接通延时定时器（TON），适用于嵌入式环境
 *
 * 该函数实现接通延时定时器（Timer On-Delay），在启动输入 IN
 * 的信号上升沿（从“0”变为“1”）时启动。
 * 定时器启动后，记录启动时间（start_time），使用时间差（ET -
 * start_time）计算当前时间值 ET。 当 ET >= PT 时，置位输出 Q 为“1”；当 IN = 0
 * 时，Q 和 ET 复位为“0”。 如果检测到新的 IN 上升沿，定时器重新启动，ET 重置为
 * 0。 ET 由定时中断提供（system_time），需单调递增，单位为毫秒。 如果 PT
 * 不是正数，Q 和 ET 复位为 0，返回 false。 注意：Time 为 int32_t，system_time
 * 在约24.85天后溢出，可能导致 ET 计算错误，后期可优化为 uint32_t 或 int64_t。
 *
 * @param IN 启动输入（true 表示“1”，false 表示“0”）
 * @param PT 接通延时的持续时间（毫秒，必须为正数）
 * @param ET 当前时间值（毫秒，由定时中断提供，需单调递增）
 * @param instance 指向 IEC_Timer 结构体的指针，存储定时器实例数据
 * @return Bool 返回 instance->Q（输出状态）
 */

// typedef struct {
//  Bool IN;              // 0启动输入
//  Time PT;              // 1接通延时的持续时间（毫秒，必须为正数）
//  Time ET;              // 3当前时间值（毫秒，相对时间）
//  Bool Q;               // 5输出
//  Bool last_in;         // 6上一次 IN 状态，用于上升沿检测
//  Time start_time;      // 7定时器启动时间
//  Bool is_running;      // 9定时器运行状态
//} IEC_Timer;
Bool TON(Bool IN, Time PT, Time ET, IEC_Timer* instance) {
  // 更新实例的 IN 、PT
  instance->IN = IN;
  instance->PT = PT;

  // 确保 PT 为正数
  if(PT < 0) {
    instance->Q          = false;
    instance->ET         = 0;
    instance->is_running = false;
    instance->last_in    = IN;
    return false;
  }

  // 检测 IN 的上升沿：上一次 IN 为 0，当前 IN 为 1
  Bool rising_edge = !instance->last_in && IN;
  // 1.定时器启动时状态更新
  if(rising_edge) {
    // 上升沿触发，启动定时器，记录启动时间
    instance->start_time = ET;
    instance->is_running = true;
    instance->ET         = 0;
  }

  // 2.定时器运行时状态更新
  if(IN && instance->is_running) {
    // IN 为 1，计算时间差，更新 ET
    int64_t diff_ms = (int64_t) ET - (int64_t) instance->start_time;
    instance->ET    = (int32_t) diff_ms;
    if(instance->ET >= PT) {
      instance->Q  = true; // 达到或超过 PT，置位 Q
      instance->ET = PT;   // ET 停止在 PT
    } else {
      instance->Q = false; // 未达到 PT，Q 保持 0
    }
  } else {
    // IN 为 0 或定时器未运行，复位 Q 和 ET
    instance->Q          = false;
    instance->ET         = 0;
    instance->is_running = false;
  }

  // 更新 last_in 为当前 IN 状态
  instance->last_in = IN;

  return instance->Q;
}

/**
 * @brief 启动接通延时定时器（CoilTON），适用于嵌入式环境
 *
 * 该函数实现“启动接通延时定时器”指令，在输入 in
 * 的信号上升沿（从“0”变为“1”）时启动 IEC 定时器。
 * 定时器运行指定的接通延时时间（value）。当 in = 1 且 ET >= value 时，输出 Q
 * 置位为“1”。 若 in 在计时结束前变为“0”，复位定时器，Q = 0，ET =
 * 0。新上升沿重新启动定时器。 定时器状态保存在 operand->Q
 * 中，可通过常开/常闭触点查询。 使用 system_time（由定时中断累加）计算相对时间
 * ET = system_time - start_time。 如果 value 不是正数，Q 和 ET 复位为 0，返回
 * false。 注意：Time 为 int32_t，system_time 在约24.85天后溢出，可能导致 ET
 * 计算错误，后期可优化为 uint32_t 或 int64_t。
 *
 * @param in RLO 输入（true 表示“1”，false 表示“0”）
 * @param value 接通延时的持续时间（毫秒，必须为正数，<操作数 1>）
 * @param system_time 当前系统时间（毫秒，由定时中断提供，需单调递增）
 * @param operand 指向 IEC_Timer 结构体的指针，存储定时器实例数据（<操作数 2>）
 * @return Bool 返回 operand->Q（输出状态）
 */
Bool CoilTON(Bool in, Time value, Time system_time, IEC_Timer* operand) {
  // 更新实例的 IN 和 PT
  operand->IN = in;
  operand->PT = value;

  // 确保 value 为正数
  if(value < 0) {
    operand->Q          = false;
    operand->ET         = 0;
    operand->is_running = false;
    operand->last_in    = in;
    return false;
  }

  // 检测 in 的上升沿
  Bool rising_edge = !operand->last_in && in;

  if(rising_edge) {
    // 上升沿触发，启动定时器，记录启动时间
    operand->start_time = system_time;
    operand->is_running = true;
    operand->ET         = 0;
  }

  if(in && operand->is_running) {
    // in 为 1，定时器运行，计算时间差
    int64_t diff_ms = (int64_t) system_time - (int64_t) operand->start_time;
    operand->ET     = (int32_t) diff_ms;
    if(operand->ET >= value) {
      operand->Q  = true;  // 计时结束，Q 置位
      operand->ET = value; // ET 停止在 value
    } else {
      operand->Q = false; // 未到时间，Q 保持 0
    }
  } else {
    // in 为 0 或定时器未运行，复位定时器
    operand->Q          = false;
    operand->ET         = 0;
    operand->is_running = false;
  }

  // 更新 last_in
  operand->last_in = in;

  return operand->Q;
}

Bool ResetIECTimerCoil(Bool in, IEC_Timer* operand) {
  if(!in || !operand)
    return false;
  operand->IN         = false;
  operand->PT         = 0;
  operand->Q          = false;
  operand->ET         = 0;
  operand->is_running = false;
  operand->last_in    = false;
  operand->start_time = 0;
  operand->last_ET    = 0;
  return true;
}
/// ST 脚本调用
void RESET_TIMER(IEC_Timer* operand) {
  if(!operand)
    return;
  operand->IN         = false;
  operand->PT         = 0;
  operand->Q          = false;
  operand->ET         = 0;
  operand->is_running = false;
  operand->last_in    = false;
  operand->last_ET    = 0;
  operand->start_time = 0;
}
/*************下面的函数是临时加的，没有验证***************/
/**
 * @brief 保持型接通延时定时器（TONR），适用于嵌入式环境
 *
 * TONR 定时器在 IN 为 true 时开始计时，并累计 ET（当前时间 - 启动时间），
 * 如果 ET >= PT，则置位输出 Q。与 TON 不同，IN 为 false 时不会清除 ET，而是保持当前值，
 * 下次 IN = true 时继续计时。适用于累计型延时。
 *
 * @param IN        启动输入（true 表示“1”，false 表示“0”）
 * @param PT        预设延时时间（毫秒，必须为正数）
 * @param ET_now    当前系统时间（毫秒，由定时中断提供，需单调递增）
 * @param instance  指向 IEC_Timer 结构体的指针，存储定时器状态
 * @return Bool     返回 instance->Q（输出状态）
 */
// Bool TONR(Bool IN, Time PT, Time ET_now, IEC_Timer* instance) {
//   // 更新输入和预设时间
//   instance->IN = IN;
//   instance->PT = PT;

//   // 无效 PT，立即复位
//   if (PT < 0) {
//     instance->Q = false;
//     instance->is_running = false;
//     // ET 保持不变（TONR 特性），不清零
//     instance->last_in = IN;
//     return false;
//   }

//   if (IN) {
//     // 启动计时（如果尚未开始）
//     if (!instance->is_running) {
//       instance->start_time = ET_now - instance->ET; // 继续上次中断时的 ET
//       instance->is_running = true;
//     }

//     // 更新累计时间 ET
//     instance->ET = ET_now - instance->start_time;

//     if (instance->ET >= PT) {
//       instance->Q  = true;
//       instance->ET = PT; // 固定在 PT
//     } else {
//       instance->Q = false;
//     }
//   } else {
//     // IN 为 false，不运行，但保留 ET，Q 清零
//     instance->is_running = false;
//     instance->Q = false;
//     // instance->ET 不变
//   }

//   instance->last_in = IN;
//   return instance->Q;
// }

/**
 * @brief 保持型接通延时定时器（TONR），支持外部复位
 *
 * IN 为 1 时开始计时，ET 累加且不会在 IN 变为 0 时清除；
 * 直到 ET >= PT，Q 才变为 1；
 * 若设置 R = true，则 Q 和 ET 被清零。
 *
 * @param IN 启动输入（true 表示“1”）
 * @param PT 预置时间（毫秒）
 * @param system_time 当前系统时间（毫秒）
 * @param instance IEC_Timer 指针
 * @param R 复位信号（true 时清除状态）
 * @return Bool 输出 Q 状态
 */
Bool TONR(Bool IN, Bool R, Time PT, Time system_time, Time* ET, IEC_Timer* instance) {
  instance->IN = IN;
  instance->PT = PT;

  if(PT < 0 || R) {
    instance->Q          = false;
    instance->ET         = 0;
    instance->last_ET    = 0;
    instance->is_running = false;
    instance->last_in    = IN;
    return false;
  }

  Bool rising_edge = !instance->last_in && IN;

  if(rising_edge) {
    instance->start_time = system_time;
    instance->is_running = true;
    instance->ET         = 0;
  }
  Bool falling_edge = instance->last_in && !IN;
  if(falling_edge) {
    instance->is_running = false;

    instance->last_ET = instance->ET;
  }
  if(IN) {
    if(instance->is_running) {
      int64_t diff_ms = (int64_t) system_time - (int64_t) instance->start_time;
      instance->ET    = (int32_t) diff_ms + instance->last_ET; // NOTE ET需要累加之前的累计值
    }
    if(instance->ET >= PT) {
      instance->Q  = true;
      instance->ET = PT;
    } else {
      instance->Q = false;
    }
  }

  // 注意：即使 IN = 0，ET 仍保留，Q 保持状态（除非被复位）
  *ET = instance->ET;

  instance->last_in = IN;
  return instance->Q;
}

/**
 * @brief 保持型接通延时定时器（CoilTONR），适用于嵌入式环境
 *
 * 该函数实现“保持型接通延时定时器”指令。在输入 in 为真时开始累加时间，
 * 当 ET ≥ value 时，输出 Q 置位为 1。若 in 变为假，Q 复位为 0，但 ET 保持。
 * 可用于多个输入中断后续再继续的情况。需要 system_time 单调递增。
 *
 * @param in RLO 输入（true 表示“1”，false 表示“0”）
 * @param value 接通延时的持续时间（毫秒，必须为正数，<操作数 1>）
 * @param system_time 当前系统时间（毫秒，由定时中断提供，需单调递增）
 * @param operand 指向 IEC_Timer 结构体的指针，存储定时器实例数据（<操作数 2>）
 * @return Bool 返回 operand->Q（输出状态）
 */
Bool CoilTONR(Bool in, Time value, Time system_time, IEC_Timer* operand) {
  // 更新 IN 和 PT
  operand->IN = in;
  operand->PT = value;

  // 非法 value，直接复位定时器
  if(value < 0) {
    operand->Q          = false;
    operand->ET         = 0;
    operand->is_running = false;
    operand->last_in    = in;
    return false;
  }

  // 检测 in 的上升沿（可用于重新启动）
  Bool rising_edge = !operand->last_in && in;

  if(rising_edge) {
    // 启动定时器，仅在上升沿重设起始时间
    operand->start_time = system_time;
    operand->is_running = true;
    operand->ET         = 0;
  }
  Bool falling_edge = operand->last_in && !in;
  if(falling_edge) {
    operand->is_running = false;
    operand->last_ET    = operand->ET;
  }

  if(in) {
    // in 为 1，持续计时
    if(operand->is_running) {
      // 如果中途 in 恢复为 1，允许继续（保持型）
      int64_t diff_ms = (int64_t) system_time - (int64_t) operand->start_time;
      operand->ET     = (int32_t) diff_ms + operand->last_ET; // 累计接通时间
    }
  }

  if(operand->ET >= value) {
    operand->Q  = true;
    operand->ET = value; // 限制 ET 为最大值
  } else {
    operand->Q = false;
  }
  // 更新 last_in
  operand->last_in = in;

  return operand->Q;
}

/**
 * @brief 断开延时定时器（TOF），适用于嵌入式环境
 *
 * 实现西门子 TOF（Timer Off-Delay）功能：当输入 IN 从 1 变为 0 时启动定时器，
 * 在持续 PT 时间后输出 Q 才变为 0。若期间 IN 再变为 1，则终止定时器，Q 再次置位。
 * 适用于继电器断电保持延时场景。
 *
 * @param IN 输入（true 表示“1”，false 表示“0”）
 * @param PT 延时时长（毫秒，必须为正数）
 * @param ET 当前系统时间（毫秒，由系统定时器提供，需单调递增）
 * @param instance 指向 IEC_Timer 结构体的指针，存储定时器实例数据
 * @return Bool 返回 instance->Q（输出状态）
 */
Bool TOF(Bool IN, Time PT, Time ET, IEC_Timer* instance) {
  // 更新输入状态
  instance->IN = IN;
  instance->PT = PT;

  // PT 非法处理
  if(PT < 0) {
    instance->Q          = false;
    instance->ET         = 0;
    instance->is_running = false;
    instance->last_in    = IN;
    return false;
  }

  // 检测下降沿（IN 从 1 变为 0）
  Bool falling_edge = instance->last_in && !IN;

  if(IN) {
    // 输入为 1，立即置位 Q，停止定时器
    instance->Q          = true;
    instance->ET         = 0;
    instance->is_running = false;
  } else if(falling_edge) {
    // 输入从 1 到 0，启动定时器
    instance->start_time = ET;
    instance->is_running = true;
    instance->ET         = 0;
    instance->Q          = true; // 延时未结束，Q 仍为 1
  }

  // 当输入为 0 且定时器正在运行
  if(!IN && instance->is_running) {
    int64_t diff_ms = (int64_t) ET - (int64_t) instance->start_time;
    instance->ET    = (int32_t) diff_ms;

    if(instance->ET >= PT) {
      instance->Q          = false; // 延时结束，Q 清零
      instance->ET         = PT;    // 固定为最大值
      instance->is_running = false; // 停止定时器
    }
  }

  // 更新 last_in 记录当前输入状态
  instance->last_in = IN;

  return instance->Q;
}

/**
 * @brief 启动断开延时定时器（CoilTOF），适用于嵌入式环境
 *
 * 实现西门子 TOF（Timer Off-Delay）逻辑：
 * - 当 in = 1 时，输出 Q = 1；
 * - 当 in 从 1 变为 0 后开始计时；
 * - 在持续 value 时间后，Q 才变为 0；
 * - 若期间 in 再次为 1，则取消定时器，Q 保持为 1；
 * - 定时器状态保存在 operand->Q，可用于常开/常闭触点查询。
 *
 * @param in          当前 RLO 输入（true 表示“1”，false 表示“0”）
 * @param value       延时时间（毫秒，必须为正数）<操作数1>
 * @param system_time 当前系统时间（毫秒，由定时中断累加）<隐含>
 * @param operand     指向 IEC_Timer 实例，用于存储定时器状态 <操作数2>
 * @return Bool       返回 operand->Q（输出状态）
 */
Bool CoilTOF(Bool in, Time value, Time system_time, IEC_Timer* operand) {
  // 更新输入和预置时间
  operand->IN = in;
  operand->PT = value;

  // 如果延时时间非法，立即复位
  if(value < 0) {
    operand->Q          = false;
    operand->ET         = 0;
    operand->is_running = false;
    operand->last_in    = in;
    return false;
  }

  // 检测 in 的下降沿（从 1 到 0）
  Bool falling_edge = operand->last_in && !in;

  if(in) {
    // 输入为 1，立即置位 Q，终止计时
    operand->Q          = true;
    operand->ET         = 0;
    operand->is_running = false;
  } else if(falling_edge) {
    // 下降沿触发，启动延时关闭
    operand->start_time = system_time;
    operand->is_running = true;
    operand->ET         = 0;
    operand->Q          = true; // 延时尚未结束，Q 保持 1
  }

  // 处理计时逻辑
  if(!in && operand->is_running) {
    int64_t diff_ms = (int64_t) system_time - (int64_t) operand->start_time;
    operand->ET     = (int32_t) diff_ms;

    if(operand->ET >= value) {
      operand->Q          = false; // 延时结束，Q 清零
      operand->ET         = value; // ET 固定为最大值
      operand->is_running = false; // 停止定时器
    }
  }

  // 保存输入状态用于下次比较
  operand->last_in = in;

  return operand->Q;
}

// TODO 待测试
/**
 * @brief 启动脉冲定时器 CoilTP
 *
 * @param IN : 输入信号
 * @param TagTime 设定时间（毫秒）
 * @param tp 定时器实例
 * @param now 当前软时钟计数（无符号，支持溢出）
 * @return Bool
 */
Bool CoilTP(Bool IN, Time TagTime, IEC_Timer* tp, Time now) {
  // 保存输入状态到结构体（方便外部调试/查询）
  tp->IN = IN;

  // 检测上升沿（0 -> 1）
  if(IN && !tp->last_in && !tp->is_running) {
    tp->is_running = 1;
    tp->start_time = now;
    tp->PT         = TagTime;
    tp->Q          = 1;
  }

  if(tp->is_running) {
    // 用无符号差值处理溢出
    tp->ET = (uint32_t) (now - tp->start_time);

    if(tp->ET >= tp->PT) {
      if(!IN) {
        tp->PT         = 0; // 当输入为0且输出完全，将pt置为0
        tp->is_running = 0;
      }
      tp->Q  = 0;
      tp->ET = tp->PT; // 到达设定值后保持 PT
    }
  } else {
    tp->ET = 0;
  }

  // 记录当前输入，供下一周期检测上升沿
  tp->last_in = IN;
  return tp->Q;
}
